package com.gitee.sparta.common.helpers;

import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;

/**
 * <p>
 *  问题：消息延迟消费，理论消费时间与实际消费时间是有偏差的，为了降低这种偏差，需要引入一些特殊的处理方式
 *  处理思路：通过消费端的理论时间与实际消费时间的偏差，来确定下一次搬运的时间是否需要提前
 *  处理逻辑：通过一个环形数组，记录每一次偏差时间，因为受网络波动的影响，偏差时间会有波动。
 *      所以需要存储多次偏差时间进行取平均值，下一次搬运的时候，就提前一个平均值即可
 * </p>
 *
 * @author zyred
 * @since v 0.0.3
 */
@Slf4j
public class NetworkDelayHelper {

    /** 环形数组的大小：实验结果 30 最合理 **/
    static final int ARRAY_LENGTH = 30;
    /** 最大延迟，不能超过 50ms，超过了则会不认可 **/
    static final int MAX_DELAY = 50;
    /** 是否初始化过 **/
    static boolean initial = false;
    /** 偏差时间最能被接收的上限和下限 **/
    static long[] scope = new long[] {0, 5};
    /** 消费的延迟时间 **/
    static final int[] CONSUMER_ARR = new int[ARRAY_LENGTH];
    /** 环形数组的偏移量 **/
    static int offset = 0;


    /**
     * 1. 该方法是线程不安全的，因为此处并不需要线程安全 (调用此方法是由多个线程调用)
     * 2. 该方法调用方：消费者、搬运初始化
     * 3. 该方法逻辑：
     *  3.1 初始化阶段：单线程 (修改 updated) , 初始化数组的所有值为搬运一次的耗时
     *  3.2 偏差时间小于 0 ，则说明平均值大了，需要把重新初始化数组的所有值为 平均值 + 偏差值(负数)
     *  3.3 偏差时间在可靠范围内，说明平均值很合理，将数组全部设置为平均值
     *  3.4 偏差时间超过了平均值，说明平均值笑了，需要适当的增大平均值，则设置对应 index 的值即可
     *  3.5 偏差时间没超过平均值，但是大于 0（PS：这种情况出现的几率最多），说明平均值还是小了，需要加大平均值
     *
     * @param deviation     偏差时间
     */
    public static void write (int deviation) {
        if (initial) {
            int average = ((int) Arrays.stream(CONSUMER_ARR).average().orElse(0));
            if (deviation > scope[1] && deviation <= MAX_DELAY) {
                CONSUMER_ARR[offset] = deviation;
            } else if (deviation < 0 || deviation > MAX_DELAY){
                CONSUMER_ARR[offset] = average;
            }
        } else {
            Arrays.fill(CONSUMER_ARR, deviation);
            initial = true;
        }
        offset = offset == ARRAY_LENGTH - 1 ? 0 : offset + 1;
    }


    @SuppressWarnings("all")
    public static int read() {
        return (int) Arrays.stream(CONSUMER_ARR).average().getAsDouble();
    }
}
